"""
Python高级特性学习 - 文件批处理工具
=========================

本文件实现了一个文件批处理工具，展示了Python中文件操作、数据类、类型注解、
面向对象编程等高级特性的实际应用。
该工具可以处理大量文件，支持流式读取、校验和计算、元数据获取、文件查找、
文本处理、批量重命名和去重等功能。
通过使用生成器、数据类、类型注解等特性，使代码更清晰、可维护性更高。
"""

import os
import hashlib
import glob
from typing import List, Dict, Optional, Generator, Tuple
from dataclasses import dataclass, asdict
from pathlib import Path
import time


@dataclass
class FileMetadata:
    """文件元数据数据类 - 存储文件的基本信息

    使用dataclass装饰器自动生成特殊方法，使数据类定义更简洁。
    包含文件路径、大小、修改时间、校验和等信息。
    """
    path: str
    size: int
    modified_time: float
    checksum: str
    extension: str


class FileBatchProcessor:
    """文件批处理处理器类 - 实现各种文件批处理功能

    该类封装了文件批处理的各种操作，包括文件读取、校验和计算、
    元数据获取、文件查找、文本处理、批量重命名和去重等。
    使用面向对象的方式组织代码，提高代码的可重用性和可维护性。
    """

    def __init__(self, base_directory: str) -> None:
        """初始化文件批处理器

        Args:
            base_directory (str): 基础目录路径
        """
        self.base_directory = Path(base_directory).absolute()  # 使用绝对路径

        # 确保基础目录存在
        self.base_directory.mkdir(parents=True, exist_ok=True)

    def read_file_streaming(self, file_path: str, chunk_size: int = 8192) -> Generator[bytes, None, None]:
        """流式读取文件 - 避免大文件占用过多内存

        使用生成器实现流式读取，每次只读取指定大小的数据块，
        适用于处理大文件，避免一次性加载整个文件到内存。

        Args:
            file_path (str): 文件路径
            chunk_size (int): 每次读取的字节数

        Yields:
            bytes: 文件数据块
        """
        # 如果file_path已经是绝对路径，则直接使用；否则与base_directory拼接
        if Path(file_path).is_absolute():
            file_path_obj = Path(file_path)
        else:
            file_path_obj = self.base_directory / file_path

        with open(file_path_obj, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                yield chunk

    def calculate_checksum(self, file_path: str, algorithm: str = 'md5') -> str:
        """计算文件校验和 - 用于验证文件完整性

        使用指定的哈希算法计算文件的校验和，可用于验证文件是否被修改。
        支持多种哈希算法，如MD5、SHA1、SHA256等。

        Args:
            file_path (str): 文件路径
            algorithm (str): 哈希算法名称

        Returns:
            str: 文件校验和
        """
        # 创建哈希对象
        hash_obj = hashlib.new(algorithm)

        # 流式读取文件并更新哈希值
        for chunk in self.read_file_streaming(file_path):
            hash_obj.update(chunk)

        return hash_obj.hexdigest()

    def get_file_metadata(self, file_path: str) -> FileMetadata:
        """获取文件元数据 - 获取文件的基本信息

        获取文件的路径、大小、修改时间、校验和和扩展名等信息，
        并封装为FileMetadata数据类对象。

        Args:
            file_path (str): 文件路径

        Returns:
            FileMetadata: 文件元数据对象
        """
        # 如果file_path已经是绝对路径，则直接使用；否则与base_directory拼接
        if Path(file_path).is_absolute():
            file_path_obj = Path(file_path)
        else:
            file_path_obj = self.base_directory / file_path

        stat_info = file_path_obj.stat()

        return FileMetadata(
            path=str(file_path_obj),
            size=stat_info.st_size,
            modified_time=stat_info.st_mtime,
            checksum=self.calculate_checksum(str(file_path_obj)),  # 传递绝对路径
            extension=file_path_obj.suffix.lower()
        )

    def find_files(self, pattern: str, recursive: bool = True) -> List[str]:
        """查找文件 - 根据模式查找匹配的文件

        使用glob模块根据指定模式查找文件，支持递归查找。
        可用于批量处理符合特定条件的文件。

        Args:
            pattern (str): 文件匹配模式
            recursive (bool): 是否递归查找

        Returns:
            List[str]: 匹配的文件路径列表
        """
        # 如果pattern已经是绝对路径，则直接使用；否则与base_directory拼接
        if Path(pattern).is_absolute():
            search_pattern = str(Path(pattern))
        else:
            search_pattern = str(self.base_directory / pattern)

        if recursive:
            # 递归查找所有匹配的文件
            return glob.glob(search_pattern, recursive=True)
        else:
            # 只在当前目录查找
            return glob.glob(search_pattern)

    def process_text_files(self, encoding: str = 'utf-8') -> Generator[Tuple[str, int, int], None, None]:
        """处理文本文件 - 统计文本文件的行数和字符数

        查找所有.txt文件，统计每个文件的行数和字符数。
        使用生成器返回结果，避免一次性加载所有数据。

        Args:
            encoding (str): 文件编码

        Yields:
            Tuple[str, int, int]: (文件路径, 行数, 字符数)
        """
        # 查找所有.txt文件
        txt_files = self.find_files("**/*.txt", recursive=True)

        for file_path in txt_files:
            try:
                lines = 0
                characters = 0

                # 确保使用绝对路径打开文件
                if not Path(file_path).is_absolute():
                    full_path = str(self.base_directory / file_path)
                else:
                    full_path = file_path

                # 逐行读取文件并统计
                with open(full_path, 'r', encoding=encoding) as f:
                    for line in f:
                        lines += 1
                        characters += len(line)

                yield (full_path, lines, characters)
            except Exception as e:
                print(f"处理文件 {file_path} 时出错: {e}")

    def batch_rename(self, pattern: str, new_name_pattern: str) -> Dict[str, str]:
        """批量重命名文件 - 批量修改文件名

        根据指定模式查找文件，并按照新的命名模式重命名。
        支持使用序号进行重命名，避免文件名冲突。

        Args:
            pattern (str): 文件查找模式
            new_name_pattern (str): 新文件名模式，使用{}作为序号占位符

        Returns:
            Dict[str, str]: 重命名映射 {原文件名: 新文件名}
        """
        files = self.find_files(pattern)
        rename_map = {}

        for i, old_file_path in enumerate(files, 1):
            # 确保使用绝对路径处理文件
            if Path(old_file_path).is_absolute():
                old_path = Path(old_file_path)
            else:
                old_path = self.base_directory / old_file_path

            # 生成新文件名
            new_name = new_name_pattern.format(i)
            new_file_path = old_path.parent / (new_name + old_path.suffix)

            # 检查目标文件是否已存在，如果存在则添加序号
            counter = 1
            original_new_file_path = new_file_path
            while new_file_path.exists():
                new_name_with_counter = f"{new_name}_{counter}"
                new_file_path = old_path.parent / (new_name_with_counter + old_path.suffix)
                counter += 1

            # 重命名文件
            try:
                old_path.rename(new_file_path)
                rename_map[str(old_path)] = str(new_file_path)
                print(f"重命名: {old_path.name} -> {new_file_path.name}")
            except Exception as e:
                print(f"重命名文件 {old_path.name} 时出错: {e}")

        return rename_map

    def find_duplicates(self, pattern: str = "**/*") -> List[List[str]]:
        """查找重复文件 - 根据校验和查找重复文件

        计算所有文件的校验和，将校验和相同的文件归为一组，
        从而找出重复文件。

        Args:
            pattern (str): 文件查找模式

        Returns:
            List[List[str]]: 重复文件组列表，每组包含校验和相同的文件路径
        """
        # 存储校验和到文件路径的映射
        checksum_map: Dict[str, List[str]] = {}

        # 查找所有文件
        all_files = self.find_files(pattern, recursive=True)

        # 计算每个文件的校验和
        for file_path in all_files:
            try:
                # 只处理文件，跳过目录
                if Path(file_path).is_file():
                    # 确保使用绝对路径计算校验和
                    if Path(file_path).is_absolute():
                        full_path = file_path
                    else:
                        full_path = str(self.base_directory / Path(file_path))
                    checksum = self.calculate_checksum(full_path)
                    if checksum not in checksum_map:
                        checksum_map[checksum] = []
                    checksum_map[checksum].append(full_path)
            except Exception as e:
                print(f"计算文件 {file_path} 校验和时出错: {e}")

        # 找出重复文件组（校验和相同的文件超过1个）
        duplicates = [paths for paths in checksum_map.values() if len(paths) > 1]

        return duplicates


def create_sample_files(base_dir: str) -> None:
    """创建示例文件 - 用于演示文件批处理功能

    创建一些示例文件，包括文本文件和二进制文件，
    用于演示文件批处理工具的各种功能。

    Args:
        base_dir (str): 基础目录路径
    """
    base_path = Path(base_dir).absolute()  # 使用绝对路径
    base_path.mkdir(parents=True, exist_ok=True)

    # 创建文本文件
    sample_texts = [
        "这是第一个示例文件。\n包含多行文本内容。\n用于演示文本处理功能。",
        "第二个示例文件。\n也包含多行文本。\n可以测试行数和字符数统计功能。",
        "第三个示例文件。\n用来演示重复文件查找功能。\n内容与第一个文件相同。",
        "这是第一个示例文件。\n包含多行文本内容。\n用于演示文本处理功能。",  # 与第一个文件内容相同
    ]

    for i, content in enumerate(sample_texts, 1):
        file_path = base_path / f"sample_{i}.txt"
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)

    # 创建二进制文件
    for i in range(1, 3):
        file_path = base_path / f"binary_{i}.bin"
        with open(file_path, 'wb') as f:
            f.write(os.urandom(1024))  # 写入1KB随机数据

    print("示例文件创建完成")


def main() -> None:
    """主函数 - 演示文件批处理工具的使用

    创建示例文件，然后演示文件批处理工具的各种功能，
    包括元数据获取、文本处理、批量重命名和重复文件查找等。
    """
    print("Python文件批处理工具演示\n" + "=" * 30)

    # 创建示例文件
    base_dir = "sample_files"
    create_sample_files(base_dir)

    # 获取base_dir的绝对路径
    base_dir_abs = os.path.abspath(base_dir)

    # 创建文件批处理器
    processor = FileBatchProcessor(base_dir_abs)

    # 1. 获取文件元数据
    print("\n1. 文件元数据获取:")
    txt_files = processor.find_files("*.txt")
    for file_path in txt_files[:2]:  # 只显示前两个文件
        try:
            metadata = processor.get_file_metadata(file_path)
            print(f"文件: {metadata.path}")
            print(f"  大小: {metadata.size} 字节")
            print(f"  修改时间: {time.ctime(metadata.modified_time)}")
            print(f"  校验和: {metadata.checksum[:16]}...")  # 只显示前16个字符
            print(f"  扩展名: {metadata.extension}")
        except Exception as e:
            print(f"获取文件 {file_path} 元数据时出错: {e}")

    # 2. 处理文本文件
    print("\n2. 文本文件处理:")
    for file_path, lines, characters in processor.process_text_files():
        print(f"文件: {file_path}")
        print(f"  行数: {lines}")
        print(f"  字符数: {characters}")

    # 3. 批量重命名
    print("\n3. 批量重命名:")
    # 使用绝对路径模式
    rename_map = processor.batch_rename(os.path.join(base_dir_abs, "*.bin"), "renamed_binary_{}")
    if not rename_map:
        print("没有找到匹配的文件进行重命名")

    # 4. 查找重复文件
    print("\n4. 重复文件查找:")
    # 使用绝对路径模式
    duplicates = processor.find_duplicates(os.path.join(base_dir_abs, "**/*"))
    if duplicates:
        for i, group in enumerate(duplicates, 1):
            print(f"重复组 {i}:")
            for file_path in group:
                print(f"  {file_path}")
    else:
        print("没有找到重复文件")

    print("\n文件批处理工具演示完成！")


if __name__ == "__main__":
    main()